using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using Cysharp.Threading.Tasks;
using HybridCLR;
using UnityEngine;
using YooAsset;

namespace GameEntry
{
    public class GameLoader : MonoBehaviour, IQueryServices, IGameLoader
    {
        /// <summary>
        /// 加载起配置实例
        /// </summary>
        public GameLoaderConfiguration configuration;

        /// <summary>
        /// 当前游戏逻辑程序集版本号
        /// </summary>
        public string CurrentLogicAssemblyVersion { get; private set; }

        /// <summary>
        /// 当前游戏逻辑程序集列表
        /// </summary>
        public IReadOnlyList<Assembly> CurrentLogicAssembles => _currentLogicAssembles;

        /// <summary>
        /// 加载游戏逻辑程序集
        /// </summary>
        public async UniTask LoadGameLogicAssemblies()
        {
            var resourcePackage = await GetOrCreateResourcePackage();

            var operation = resourcePackage.LoadAssetSync<GameAssemblyManifest>(configuration.assemblyManifestAssetKey);
            var manifest = operation.GetAssetObject<GameAssemblyManifest>();
            if (manifest == null)
                throw new Exception($"加载程序集清单文件失败: {operation.LastError}");

            // 补充元数据
            LoadAOTPatchMetadata(resourcePackage, manifest);

            // 加载程序集
            LoadAssemblies(resourcePackage, manifest);

            var entryTypeName = manifest.gameLogicEntryTypeName;
            var entryMethodName = manifest.gameLogicEntryStaticMethodName;

            operation.Release();

            // 启动游戏逻辑程序集入口
            LaunchGameLogicEntry(entryTypeName, entryMethodName);
        }

        #region [内部实现]

        private void Awake()
        {
            DontDestroyOnLoad(gameObject);
        }

        private void Start()
        {
            BetterStreamingAssets.Initialize();
            YooAssets.Initialize();

            if (configuration == null)
                throw new Exception("请配置加载器配置实例");

            LoadGameLogicAssemblies().Forget();
        }

        private void OnDestroy()
        {
            YooAssets.Destroy();
        }

        /// <summary>
        /// 
        /// </summary>
        private async UniTask<ResourcePackage> GetOrCreateResourcePackage()
        {
            var resourcePackage = YooAssets.TryGetPackage(configuration.assetPackageName);
            if (resourcePackage != null)
                return resourcePackage;

            resourcePackage = YooAssets.CreatePackage(configuration.assetPackageName);
            YooAssets.SetDefaultPackage(resourcePackage);

            // ReSharper disable once JoinDeclarationAndInitializer
            InitializeParameters initParameters;
#if UNITY_EDITOR && !SIMULATE_RES
            initParameters = new EditorSimulateModeParameters
            {
                SimulateManifestFilePath = EditorSimulateModeHelper.SimulateBuild(configuration.assetPackageName)
            };
#else
            initParameters = new HostPlayModeParameters
            {
                DefaultHostServer = configuration.mainHostServer,
                FallbackHostServer = configuration.fallbackHostServer,
                QueryServices = this,
            };
            if (!string.IsNullOrEmpty(configuration.decryptionServiceTypeName))
            {
                var type = Type.GetType(configuration.decryptionServiceTypeName);
                if (type != null && typeof(IDecryptionServices).IsAssignableFrom(type))
                    initParameters.DecryptionServices = (IDecryptionServices)Activator.CreateInstance(type);
            }
#endif
            var initOperation = resourcePackage.InitializeAsync(initParameters);
            await initOperation.ToUniTask();

            if (initOperation.Status != EOperationStatus.Succeed)
                throw new Exception($"初始化资源包失败: {initOperation.Error}");

            return resourcePackage;
        }

        /// <summary>
        /// 补充元数据
        /// </summary>
        private void LoadAOTPatchMetadata(ResourcePackage resourcePackage, GameAssemblyManifest manifest)
        {
            foreach (var assemblyName in manifest.aotPatchAssemblyNames)
            {
                var assetKey = manifest.GetAOTPatchAssemblyAssetKey(assemblyName);
                var rawOperation = resourcePackage.LoadRawFileSync(assetKey);

                if (rawOperation.Status != EOperationStatus.Succeed)
                    throw new Exception($"初始化游戏失败: {rawOperation.LastError}");

                var bytes = rawOperation.GetRawFileData();
                var err = RuntimeApi.LoadMetadataForAOTAssembly(bytes, HomologousImageMode.SuperSet);
                if (err != LoadImageErrorCode.OK)
                    throw new Exception($"初始化游戏失败: {err}");

                rawOperation.Release();
            }
        }

        /// <summary>
        /// 加载游戏程序集
        /// </summary>
        private void LoadAssemblies(ResourcePackage resourcePackage, GameAssemblyManifest manifest)
        {
            // 版本未发生变化时 无需加载程序集
            if (string.Equals(CurrentLogicAssemblyVersion, manifest.version))
                return;

            CurrentLogicAssemblyVersion = manifest.version;
            _currentLogicAssembles.Clear();

#if UNITY_EDITOR
            _currentLogicAssembles.AddRange(AppDomain.CurrentDomain.GetAssemblies().Where(assembly => manifest.gameLogicAssemblyNames.Contains(assembly.GetName().Name)));
#else
            foreach (var assemblyName in manifest.gameLogicAssemblyNames)
            {
                var assetKey = manifest.GetGameLogicAssemblyAssetKey(assemblyName);
                var rawOperation = resourcePackage.LoadRawFileSync(assetKey);

                if (rawOperation.Status != EOperationStatus.Succeed)
                    throw new Exception($"初始化游戏失败: {rawOperation.LastError}");

                var bytes = rawOperation.GetRawFileData();
                _currentLogicAssembles.Add(Assembly.Load(bytes));

                rawOperation.Release();
            }
#endif

        }

        /// <summary>
        /// 启动游戏逻辑程序集入口
        /// </summary>
        private void LaunchGameLogicEntry(string entryTypeName, string entryMethodName)
        {
            Type gameLogicEntryType = null;
            foreach (var assembly in CurrentLogicAssembles)
            {
                gameLogicEntryType = assembly.GetType(entryTypeName);
                if (gameLogicEntryType != null)
                    break;
            }

            if (gameLogicEntryType == null)
                throw new Exception($"初始化游戏失败: 找不到游戏逻辑入口类:{entryTypeName}");

            var method =
                gameLogicEntryType.GetMethod(entryMethodName, BindingFlags.Public | BindingFlags.Static);

            if (method == null)
                throw new Exception($"初始化游戏失败: 找不到游戏逻辑入口静态函数:{entryMethodName}");

            method.Invoke(null, new object[] { this });
        }

        public bool QueryStreamingAssets(string fileName)
        {
            var folderName = YooAssets.GetStreamingAssetBuildinFolderName();
            return BetterStreamingAssets.FileExists($"{folderName}/{fileName}");
        }

        private readonly List<Assembly> _currentLogicAssembles = new List<Assembly>();

        #endregion
    }
}